#Pyton library imports
from itertools import product
import re
#External libraries imports
from pyparsing import *
#Internal imports
from Exceptions import SemanticException, LabelsMismatchException
import FSPNamespace as ns
from FSPExpressionParser import *
from ParsingUtilities import *
from FSPSymbolParser import FSPSymbol

def FSPtoIFSPGenerator(definitions, skipLowercaseExpressions=False):
  namespace = None
  if isinstance(definitions[0], ns.FSPNamespace):
    namespace = definitions[0]
    definitions = definitions[1:]
  else: namespace = ns.FSPNamespace(name="Global")

  if skipLowercaseExpressions:
    Expression = LowercaseIdentifier | ExpressionParser(namespace=namespace)
  else:
    Expression = ExpressionParser(namespace=namespace)

  ConstantIdentifier = FSPSymbol(uppercaseRegex, "const", namespace)

  RangeIdentifier = FSPSymbol(uppercaseRegex, "range", namespace)

  SetIdentifier = FSPSymbol(
    uppercaseRegex, "set", namespace).setParseAction(lambda x: [x.asList()])

  ParameterIdentifier = UppercaseIdentifier

  ConstantDefinition = (
      Suppress(Keyword("const")) + UppercaseIdentifier + lit("=") + Expression
      ).setParseAction(lambda s, l, t: namespace.addObject(s, l, t[0], int(t[1]),
                                                           "const"))

  def validateRangeLimits(s, l, t):
    beginning = 0
    end = 0
    try:
      beginning = eval(t[0])
      end = eval(t[1])
      if beginning > end: raise Exception()
    except:
      raise SemanticException(s, l, "Bad syntax for range expression.")
    return [range(beginning, end + 1)]

  RangeDefinition = (
      Suppress(Keyword("range")) + UppercaseIdentifier + lit("=") +
      Expression + lit("..") + Expression #devono uscire due int
    ).setParseAction(
      lambda s,l,t: [t[0]] + validateRangeLimits(s,l,t[1:]),
      lambda s, l, t: namespace.addObject(s, l, t[0], t[1], "range"))

  """problem: what if one of the limits is an expression depending on a variable?
  If so, the rangeExpression remains literal and is seen as a set of 1 label
  (the range expression itself), so it can safely pass the preprocessing phase.
  In the variable resolution phase, these unresolved expressions must be resolved."""
  def manageRangeExpression(s, l, t):
    if re.match(variableRegex, t[0]) or re.match(variableRegex, t[1]):
      return {t[0]+".."+t[1]}
    return validateRangeLimits(s, l, t)[0]

  RangeExpression = (
      (Expression + lit("..") + Expression).setParseAction(manageRangeExpression))

  Range = RangeIdentifier ^ RangeExpression

  ActionLabels = Forward()

  def uniquify(x):
    seen = {}
    result = []
    for item in x:
      if item in seen: continue
      seen[item] = 1
      result.append(item)
    return result

  SetElements = (ActionLabels + ZeroOrMore(lit(",") + ActionLabels)
    ).setParseAction(lambda l: [uniquify(l.asList())])

  SetExpression = (lit("{") + SetElements + lit("}"))

  SetDefinition = (
    Suppress(Keyword("set")) + UppercaseIdentifier + lit("=") + SetExpression
  ).setParseAction(
    lambda s, l, t: namespace.addObject(s, l, t[0], t[1], "set"))

  Set = SetIdentifier ^ SetExpression

  SquaredExpression = (
      lit("[") + LowercaseIdentifier + lit("]") if skipLowercaseExpressions
      else NoMatch()
    ) | (Literal("[") + Expression + Literal("]"))

  ActionLabel = (Combine(
    (LowercaseIdentifier ^ SquaredExpression) +
      ZeroOrMore((Literal(".") + LowercaseIdentifier) ^ SquaredExpression)))

  def pyListReprToFSPSetRepr(x):
    #print("a pyList2FSPSet arriva: ", x)
    return "{" + str(x)[1:-1].replace("'", "") + "}"

  VariableDefinition = (
    LowercaseIdentifier + Literal(":") +
    (
      UppercaseIdentifier ^ #set or range identifiers
      (Expression + Literal("..") + Expression)^ #range expression
      SetExpression.copy().setParseAction(lambda x: pyListReprToFSPSetRepr(x[0])) # set expression
    )).setParseAction(lambda x: [[strJoin(["{"+x[0]+"}"]+x[1:])]])

  ActionRange = (
      SetIdentifier
      ^SetExpression
      ^RangeIdentifier.copy().addParseAction(lambda t: [[str(i) for i in t[0]]])
      ^RangeExpression.copy().addParseAction(lambda t: [[str(i) for i in t[0]]])
      ^VariableDefinition)

  def expandActionLabels(components):
    components = components.asList()
    components = [[c] if type(c) == str else list(c) for c in components]
    strComponents = []
    for s in components:
      for e in s:
        if type(e) != str:
          s = [str(i) if i == e else e]
      strComponents.append(s)
    strComponents = [''.join(e) for e in product(*strComponents)]
    return strComponents

  ActionLabels << (
      (ActionLabel ^ Set ^ (Literal("[") + ActionRange + Literal("]"))) +
      ZeroOrMore(
        (Literal(".") + ActionLabel) ^
        (Literal(".") + Set) ^
        (Literal("[") + ActionRange + Literal("]")) ^
        Combine(Literal("[") + Expression + Literal("]")))
    ).setParseAction(expandActionLabels)

  PrefixActions = (
    Group(ActionLabels) + ZeroOrMore(Literal("->") + Group(ActionLabels))
    ).setParseAction(expandActionLabels)

  Guard = Group(Keyword("when") + Expression)

  LocalProcess = Forward()

  def expandActions(x):
    returnList = []
    if x[0][0] == "when":
      if not re.match(variableRegex, x[0][1]):
        if eval(x[0][1]) == 0:
          return [""]
        else:
          for action in x[1]:
            returnList = returnList + [action] + ["->"] + [x[3]] + ["|"]
      else:
        for action in x[1]:
          returnList = returnList + ["when "] + [x[0][1]] + [" ", action] + ["->"] + [x[3]] + ["|"]
          #print("La mi' roba:", returnList)
    else:
        for action in x[0]:
          returnList = returnList + [action] + ["->"] + [x[2]] + ["|"]
    if returnList[-1] == "|": del returnList[-1]
    return strJoin(returnList)

  ActionPrefix = (Optional(Guard) + Group(PrefixActions) +
                  Literal("->") + LocalProcess).setParseAction(expandActions)

  Indices = OneOrMore(Literal("[") + Expression + Literal("]"))

  IndexRanges = OneOrMore(
    (Literal("[") + Expression + Literal("]")).setParseAction(lambda x: [[strJoin(x)]]) ^
    (lit("[") + ActionRange + lit("]")).setParseAction(
      lambda x: [["[" + string + "]" for actRange in x for string in actRange]]))

  ParameterDefinition = ParameterIdentifier + Literal("=") + Expression

  Parameters = (Literal("(") +
    ParameterDefinition + ZeroOrMore(lit(",") + ParameterDefinition) +
    Literal(")"))

  BaseLocalProcess =\
    (
      Literal("END") ^ Literal("STOP") ^ Literal("ERROR") ^
      (UppercaseIdentifier + Optional(Indices))
    )

  Choice = (ActionPrefix + ZeroOrMore(lit("|") + ActionPrefix)).setParseAction(
  lambda x: addSeparator(x, "|"), strJoin)

  LocalProcess <<\
    (
      BaseLocalProcess ^
      #SequentialComposition ^
      #if Expression then LocalProcess ^
      #if Expression then LocalProcess else LocalProcess ^
      (Literal("(") + Choice + Literal(")"))
    ).setParseAction(strJoin)

  def manageIndexRanges(x):
    returnList = []
    if x[1] != "=":
      #print("Individuato un gruppo di indici: ", x[1])
      for indexCombination in product(*(x[1].asList())):
        returnList = returnList + [x[0]]
        for index in indexCombination:
          returnList = returnList + [index]
        returnList = returnList + ["=", x[3], ","]
      if returnList[-1] == ",": del returnList[-1]
    else:
      returnList = returnList + [x[0], "=", x[2]]
    return strJoin(returnList)

  LocalProcessDefinition = (
      UppercaseIdentifier + Group(Optional(IndexRanges)) + Literal("=") + LocalProcess
    ).setParseAction(manageIndexRanges, strJoin)

  ProcessBody = (
      LocalProcess + ZeroOrMore(Literal(",") + LocalProcessDefinition)
    )

  AlphabetExtension = (Literal("+") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

  def relabelToString(s, l, x):
    if len(x[0]) != len(x[1]): raise LabelsMismatchException(s, l)
    string = ""
    for i in range(0, len(x[0])):
      string = string + x[0][i] + "/" + x[1][i] + ("," if i != len(x[0]) - 1 else "")
    return string

  RelabelRule =\
    (
      Group(ActionLabels) +
      Suppress(Literal("/")) +
      Group(ActionLabels)
    ).setParseAction(lambda s,l,t: relabelToString(s,l,t))
  #oppure forall IndexRanges { RelabelDefs }

  RelabelRules = RelabelRule + ZeroOrMore(Literal(",") + RelabelRule)

  Relabel = Literal("/") + Literal("{") + RelabelRules + Literal("}")

  SelectiveExposition = (Literal("@") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

  SelectiveHiding = (Literal("\\") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

  Hiding = SelectiveHiding ^ SelectiveExposition

  process = None

  def saveProcess(x):
    nonlocal process
    process = strJoin(x)
    return x

  ProcessDefinition =\
    (
      UppercaseIdentifier + Optional(Parameters) + Literal("=") +
      ProcessBody +
      Optional(AlphabetExtension) +
      Optional(Relabel) +
      Optional(Hiding) +
      Literal(".")
    ).setParseAction(saveProcess)

  Definition = (ConstantDefinition|RangeDefinition|SetDefinition|ProcessDefinition)

  """GENERATOR CODE"""
  namespaceShot = False
  for definition in definitions:
    Definition.parseString(definition, parseAll=True)
    if process:
      if not namespaceShot:
        yield namespace
        namespaceShot = True
      yield process
    process = None
